package com.pig4cloud.pigx.contract.job;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.pig4cloud.pigx.common.customize.mongo.utils.MongoUtils;
import com.pig4cloud.pigx.common.customize.redis.utils.RedisUtils;
import com.pig4cloud.pigx.common.customize.utils.id.IdWorker;
import com.pig4cloud.pigx.contract.bo.RateSettlementJobBO;
import com.pig4cloud.pigx.contract.constant.ContractRedisLockConstants;
import com.pig4cloud.pigx.contract.entity.ContractPosition;
import com.pig4cloud.pigx.contract.entity.ContractSymbol;
import com.pig4cloud.pigx.contract.entity.RippleContractFundingRateHistory;
import com.pig4cloud.pigx.contract.enums.ContractOrderEnum;
import com.pig4cloud.pigx.contract.enums.ContractPositionEnum;
import com.pig4cloud.pigx.contract.mapper.ContractPositionMapper;
import com.pig4cloud.pigx.contract.mapper.ContractSymbolMapper;
import com.pig4cloud.pigx.contract.mapper.RippleContractFundingRateHistoryMapper;
import com.pig4cloud.pigx.contract.mongo.entity.ContractSymbolMongo;
import com.pig4cloud.pigx.contract.utils.ContractUtils;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.springframework.beans.BeanUtils;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

/**
 * 每日三次 间隔8h 结算各币对资金费率
 * @date 2022/8/17 16:34
 */
@EnableScheduling
@Slf4j
@Component
@AllArgsConstructor
public class CapitalRateSettlementJob {

	private final IdWorker idWorker;

	private final MongoTemplate mongo;

	private final ContractPositionMapper positionMapper;

	private final RippleContractFundingRateHistoryMapper historyMapper;

	private final ContractSymbolMapper symbolMapper;

	private final ContractUtils utils;

	private final RedisTemplate<String,Object> redis;

	private final RedisUtils redisUtils;

	private final static String JOB_SEND_REDIS_KEY = "rate_settlement_job_send_num";

	private final static String JOB_NUM_REDIS_KEY = "rate_settlement_job_complete_num";

	private final static String ALL_AMOUNT_REDIS_KEY = "rate_settlement_job_all_amount";

	private final static String AMOUNT_REDIS_KEY = "rate_settlement_job_amount";

	private final static Long AWAIT_TIME = 3600000L;

	/**
	 * 资金费率公式
	 * I = (((ask + bid) / 2) - P0) / P0
	 * R/fee = clamp(I,a,b)
	 * I: 溢价指数
	 * a: 资金费率下限
	 * b: 资金费率上限
	 * P0: 指数价
	 * (ask + bid) / 2) = 最新价
	 * Clamp(A,B,C): 当A在B,C范围内时，取A: 否则上限取B,下限取C(B>C)
	 * @param val I 溢价指数
	 * @param min 资金费率下限
	 * @param max 资金费率上限
	 * @return 资金费率
	 */
	public BigDecimal clamp(BigDecimal val, BigDecimal min, BigDecimal max) {
		return BigDecimal.valueOf(Math.max(min.floatValue(), Math.min(max.floatValue(), val.floatValue())));
	}

	/**
	 * xx-job 任务
	 * 定时执行 每日 8:00 | 16:00 | 0:00 结算资金费率
	 */
//	@XxlJob("capitalRateSettlementJob")
	@Scheduled(cron = "0 0 0,8,16 * * ?")
	public void capitalRateSettlement() {
		// 竞争分布式锁
		RLock lock = redisUtils.getLock(ContractRedisLockConstants.RATE_SETTLEMENT_JOB, null);
		if (Objects.isNull(lock)) {
			return;
		}
		try {
			// 清除之前用到的redis缓存
			redis.delete(JOB_SEND_REDIS_KEY);
			redis.delete(JOB_NUM_REDIS_KEY);
			redis.delete(ALL_AMOUNT_REDIS_KEY);
			redis.delete(AMOUNT_REDIS_KEY);
			LocalDateTime now = LocalDateTime.now();
			// 查询当前持仓用户
			List<ContractPosition> positions = positionMapper.selectList(new QueryWrapper<ContractPosition>() {{
				eq("status", ContractPositionEnum.Status.OPEN.getValue());
				eq("is_deleted", "0");
			}});
			// 获取所有币对详情 - 资金费率
			List<ContractSymbolMongo> mongoList = mongo.find(new Query(), ContractSymbolMongo.class);
			Map<Long, BigDecimal> fundingRateMap = new HashMap<>(mongoList.size());
			Map<Long, BigDecimal> lastPriceMap = new HashMap<>(mongoList.size());
			mongoList.forEach(e -> {
				fundingRateMap.put(e.getId(),e.getFundingRate());
				lastPriceMap.put(e.getId(),e.getLastPrice());
			});
			// 更新资金费率
			initFundingRateHistory(mongoList,now);
			// 筛选需要扣钱的仓位
			List<RateSettlementJobBO> removeList = new ArrayList<>();
			List<RateSettlementJobBO> addList = new ArrayList<>();
			// 累计应分发金额
			BigDecimal sendMoney = BigDecimal.ZERO;
			for (ContractPosition position : positions) {
				// 获取资金费率
				BigDecimal decimal = fundingRateMap.get(position.getSymbolId());
				// 判断资金费率正负
				int sigNum = decimal.signum();
				// 计算将要 (扣除/增加) 的资金
				BigDecimal multiply = position.getNowNum()
						.multiply(lastPriceMap.get(position.getSymbolId()))
						.multiply(decimal.signum() > 0 ? decimal : decimal.negate());
				if ((sigNum == 1 && position.getSide().equals(ContractOrderEnum.Side.EMPTY.getValue()))
						|| (sigNum == -1 && position.getSide().equals(ContractOrderEnum.Side.MORE.getValue()))){
					removeList.add(new RateSettlementJobBO(){{
						setIsSave(false);
						setPositionId(position.getId());
						setUserId(position.getUserId());
						setFeeRate(decimal);
						setMultiply(multiply);
					}});
				} else {
					// 累计应发金额
					sendMoney = sendMoney.add(multiply);
					addList.add(new RateSettlementJobBO(){{
						setIsSave(true);
						setPositionId(position.getId());
						setUserId(position.getUserId());
						setFeeRate(decimal);
						setMultiply(multiply);
					}});
				}
			}
			// 发送扣钱任务 并 使用redis累计发送了多少个扣钱消息队列
			redis.opsForValue().set(JOB_SEND_REDIS_KEY,removeList.size());
			removeList.forEach(e -> {
				utils.sendRateSettlementJobMq(e);
			});
			// 开始循环读取redis 等待所有已发送消息队列回执成功 || 如果超过一定时间还未能获取所有消息回执,则自动结束循环
			long startTime = System.currentTimeMillis();
			while ((System.currentTimeMillis() - startTime) < AWAIT_TIME) {
				Object num = redis.opsForValue().get(JOB_NUM_REDIS_KEY);
				if (num != null && (Integer) num == removeList.size()) {
					break;
				}
			}
			// 确认扣钱任务都执行成功后 开始统计扣钱金额
			// 应扣
			Object redisAllAmount = redis.opsForValue().get(ALL_AMOUNT_REDIS_KEY);
			// 实际扣
			Object redisAmount = redis.opsForValue().get(AMOUNT_REDIS_KEY);
			if (redisAllAmount != null && redisAmount != null) {
				// 计算发钱比例
				BigDecimal divide = new BigDecimal(redisAmount.toString())
						.divide(sendMoney, 8, BigDecimal.ROUND_DOWN);
				// 如果收的钱比发的多,自己留下
				if (divide.compareTo(BigDecimal.ONE) > 0) {
					divide = BigDecimal.ONE;
				}
				// 发送 发钱任务
				for (RateSettlementJobBO bo : addList) {
					bo.setSaveProportion(divide);
					utils.sendRateSettlementJobMq(bo);
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			redisUtils.releaseLock(lock);
		}
	}

	/**
	 * 记录资金费率历史
	 */
	@Transactional(propagation = Propagation.REQUIRES_NEW,rollbackFor=Exception.class)
	public void initFundingRateHistory(List<ContractSymbolMongo> list,LocalDateTime now) {
		for (ContractSymbolMongo timingVO : list) {
			ContractSymbolMongo lineMongo = new ContractSymbolMongo();
			// 计算币对溢价指数
			BigDecimal divide = (timingVO.getLastPrice().subtract(timingVO.getIndexPrice()))
					.divide(timingVO.getIndexPrice(),8,BigDecimal.ROUND_HALF_UP);
			// 计算资金费率
			BigDecimal clamp = clamp(divide, timingVO.getMinCapitalRate(), timingVO.getMaxCapitalRate());
			lineMongo.setFundingRate(clamp);
			Query query = Query.query(Criteria.where("_id").is(timingVO.getId()));
			Update update = MongoUtils.getUpdate(lineMongo);
			mongo.updateMulti(query,update,ContractSymbolMongo.class);
			ContractSymbol symbol = new ContractSymbol();
			BeanUtils.copyProperties(lineMongo,symbol);
			symbolMapper.updateById(symbol);
			// 资金费率历史
			RippleContractFundingRateHistory history = new RippleContractFundingRateHistory();
			history.setId(idWorker.nextId());
			history.setDateTime(now);
			history.setSymbolId(timingVO.getId());
			history.setFundingRate(clamp);
			// 目前资金费率间隔为8h
			history.setFundingRateInterval(8);
			try{
				historyMapper.insert(history);
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}
}
